package com.zhou.framework.helper;

import com.zhou.framework.annotation.Aspect;
import com.zhou.framework.annotation.Service;
import com.zhou.framework.proxy.AspectProxy;
import com.zhou.framework.proxy.Proxy;
import com.zhou.framework.proxy.ProxyManager;
import com.zhou.framework.proxy.TransactionProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * 方法拦截助手类
 *
 * @author zhouplus [http://my.oschina.net/u/2303497/blog]
 * @date 2016/4/18  16:02
 * @since 1.0.0
 */

public final class AopHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);
    /**
     * 静态块，初始化整个AOP框架
     */
    static {
        try {
            Map<Class<?>,Set<Class<?>>> proxyMap = createProxyMap();
            Map<Class<?>,List<Proxy>> targetMap = createTargetMap(proxyMap);
            for (Map.Entry<Class<?>,List<Proxy>> targetEntry:
                 targetMap.entrySet()) {
                Class targetClass = targetEntry.getKey();
                List<Proxy> proxyList = targetEntry.getValue();
                Object proxy = ProxyManager.createProxy(targetClass,proxyList);
                BeanHelper.setBean(targetClass,proxy);
            }

        } catch (Exception e) {
            LOGGER.error("aop failure",e);
        }

    }



    /**
     * 获取Aspect注解中设置的注解类，若该类不是Aspect类，则可调用ClassHelper#getClassSetByAnnotaion方法获取相关类
     * 并把这些类放入目标类集合中，最终返回这个集合
     *
     * @param aspect
     * @return
     * @throws Exception
     */
    private static Set<Class<?>> createTargetClassSet(Aspect aspect) throws Exception {
        Set<Class<?>> targetClassSet = new HashSet<Class<?>>();
        Class<? extends Annotation> annotation = aspect.value();
        if (annotation != null && !annotation.equals(Aspect.class)) {
            targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
        }
        return targetClassSet;
    }

    /**
     * 代理类需要扩展AspectProxy抽象类，还需要带有Aspect注解，只有满足这两个条件，
     * 才能根据Aspect注解中所定义的注解属性去获取该注解所对应的目标类集合，
     * 然后才能建立代理类和目标类集合之间的映射关系，最终返回这个映射体系
     *
     * @return
     * @throws Exception
     */
    private static Map<Class<?>, Set<Class<?>>> createProxyMap() throws Exception {
        Map<Class<?>, Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>();
        //添加普通切面代理
        addAspectProxy(proxyMap);
        //添加事物代理
        addTransactionProxy(proxyMap);
        return proxyMap;
    }

    /**
     * 一旦获取了代理类和目标类集合之间的映射关系，就能根据这个关系分析出目标类与代理对象列表之间的映射关系
     *
     * @param proxyMap
     * @return
     * @throws Exception
     */
    private static Map<Class<?>, List<Proxy>> createTargetMap(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception {

        Map<Class<?>, List<Proxy>> targetMap = new HashMap<Class<?>, List<Proxy>>();
        for (Map.Entry<Class<?>, Set<Class<?>>> proxyEntry :
                proxyMap.entrySet()) {
            Class proxyClass = proxyEntry.getKey();
            Set<Class<?>> targetClassSet = proxyEntry.getValue();

            for (Class<?> targetClass :
                    targetClassSet) {
                Proxy proxy = (Proxy) proxyClass.newInstance();
                if (targetMap.containsKey(targetClass)) {
                    targetMap.get(targetClass).add(proxy);
                } else {
                    List<Proxy> proxyList = new ArrayList<Proxy>();
                    proxyList.add(proxy);
                    targetMap.put(targetClass, proxyList);
                }
            }
        }
        return targetMap;
    }

    /**
     * 添加普通切面代理
     * @param proxyMap
     * @throws Exception
     */
    private static void addAspectProxy(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception {
        //找到容器中AspectProxy的子类。切面类
        Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);

        for (Class<?> proxyClass :
                proxyClassSet) {
            //如果该类的注解是Aspect切面
            if (proxyClass.isAnnotationPresent(Aspect.class)) {
                //获取该Aspect注解
                Aspect aspect = proxyClass.getAnnotation(Aspect.class);
                //根据Aspect中的value值去获取所要拦截的类，即所有所用的该value注解（比如所有的controller）的类的class
                Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
                //将该切面类和使用了该切面类的实体类的类映射关系存起来
                proxyMap.put(proxyClass, targetClassSet);
            }
        }
    }

    private static void addTransactionProxy(Map<Class<?>, Set<Class<?>>> proxyMap){
        Set<Class<?>> serviceClassSet = ClassHelper.getClassSetByAnnotation(Service.class);
        proxyMap.put(TransactionProxy.class,serviceClassSet);
    }
}
